package model;

import io.ActionSender;
import io.ByteStream;

import java.util.LinkedList;

import org.xsocket.connection.INonBlockingConnection;

import pack.World;

public final class Player {
    private static final int[] DIRECTION_X = { -1, 0, 1, -1, 1, -1, 0, 1 };
    private static final int[] DIRECTION_Y = { 1, 1, 1, 0, 0, -1, -1, -1 };
    private int id;
    private int x;
    private int y;
    private int regionX;
    private int regionY;
    private boolean regionUpdate;
    private String username = "";
    private String password = "";
    private boolean isVisible;
    private INonBlockingConnection connection;
    private String hostAddress;
    private World world;
    private ByteStream in = new ByteStream(10000);
    private ByteStream out = new ByteStream(50000);
    private boolean didTeleport;
    private int walkDir = -1;
    private LinkedList<Integer> movement = new LinkedList<Integer>();
    private int[] lastCoords = new int[2];
    private String chat = "";
    private boolean chatUpdate;
    private ActionSender actionSender = new ActionSender(this);
    private LinkedList<Player> players = new LinkedList<Player>();
    private int headId = 9;
    private int torsoId = 10;
    private int legsId = 11;
    private boolean appearanceUpdate;

    public Player(INonBlockingConnection connection, World world) {
        this.connection = connection;
        hostAddress = connection.getRemoteAddress().getHostAddress();
        this.world = world;
    }

    public void setAppearance(int headId, int torsoId, int legsId) {
        this.headId = headId;
        this.torsoId = torsoId;
        this.legsId = legsId;
        appearanceUpdate = true;
    }

    public void doChat(String chat) {
        this.chat = chat;
        chatUpdate = true;
    }

    public void setCoords(int x, int y) {
        this.x = x;
        this.y = y;
        didTeleport = true;
        regionUpdate = true;
    }

    public boolean withinDistance(Player p) {
        if (p == null || !isVisible || !p.isVisible() || this == p) {
            return false;
        }
        int deltaX = p.x() - x;
        int deltaY = p.y() - y;
        return deltaX <= 15 && deltaX >= -16 && deltaY <= 15 && deltaY >= -16;
    }

    public void tick() {
        if (!movement.isEmpty()) {
            int dir = movement.removeFirst();
            x += DIRECTION_X[dir];
            y += DIRECTION_Y[dir];
            walkDir = dir;
            int localX = x - 8 * (regionX - 6);
            int localY = y - 8 * (regionY - 6);
            if (localX < 16 || localX > 87 || localY < 16 || localY > 87) {
                regionUpdate = true;
            }
        }
        if (regionUpdate) {
            regionX = x >> 3;
            regionY = y >> 3;
        }
    }

    public boolean hasUpdate() {
        return chatUpdate || appearanceUpdate;
    }

    public void clearUpdates() {
        walkDir = -1;
        didTeleport = false;
        regionUpdate = false;
        chatUpdate = false;
        appearanceUpdate = false;
    }

    public void resetMovement() {
        int localX = x - 8 * (regionX - 6);
        int localY = y - 8 * (regionY - 6);
        lastCoords = new int[] { localX, localY };
        movement.clear();
    }

    public void addMoveLocation(int x, int y) {
        x = x - (regionX - 6) * 8;
        y = y - (regionY - 6) * 8;
        int diffX = x - lastCoords[0];
        int diffY = y - lastCoords[1];
        int max = Math.max(Math.abs(diffX), Math.abs(diffY));
        for (int i = 0; i < max; i++) {
            if (diffX < 0) {
                diffX++;
            } else if (diffX > 0) {
                diffX--;
            }
            if (diffY < 0) {
                diffY++;
            } else if (diffY > 0) {
                diffY--;
            }
            int dir = direction(x - diffX - lastCoords[0], y - diffY - lastCoords[1]);
            if (dir != -1) {
                lastCoords = new int[] { x - diffX, y - diffY };
                movement.add(dir);
            }
        }
    }

    public int direction(int dx, int dy) {
        if (dx < 0) {
            if (dy < 0) {
                return 5;
            } else if (dy > 0) {
                return 0;
            } else {
                return 3;
            }
        } else if (dx > 0) {
            if (dy < 0) {
                return 7;
            } else if (dy > 0) {
                return 2;
            } else {
                return 4;
            }
        } else {
            if (dy < 0) {
                return 6;
            } else if (dy > 0) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public void findRoute(int destX, int destY, boolean moveTo) {
        int[][] via = new int[104][104];
        int[][] cost = new int[104][104];
        int[] tileQueueX = new int[4000];
        int[] tileQueueY = new int[4000];
        for (int xx = 0; xx < 104; xx++) {
            for (int yy = 0; yy < 104; yy++) {
                cost[xx][yy] = 99999999;
            }
        }
        int localX = x - 8 * (regionX - 6);
        int localY = y - 8 * (regionY - 6);
        int curX = localX;
        int curY = localY;
        via[curX][curY] = 99;
        cost[curX][curY] = 0;
        int head = 0;
        int tail = 0;
        tileQueueX[head] = curX;
        tileQueueY[head++] = curY;
        boolean foundPath = false;
        int pathLength = tileQueueX.length;
        while (tail != head) {
            curX = tileQueueX[tail];
            curY = tileQueueY[tail];
            int absX = (regionX - 6) * 8 + curX;
            int absY = (regionY - 6) * 8 + curY;
            if (!moveTo && absX == destX && absY == destY) {
                foundPath = true;
                break;
            } else if (moveTo && (absX == destX && absY == destY + 1 || absX == destX && absY == destY - 1 || absX == destX + 1
                    && absY == destY || absX == destX - 1 && absY == destY)) {
                foundPath = true;
                break;
            }
            tail = (tail + 1) % pathLength;
            int thisCost = cost[curX][curY] + 1;
            if (curY > 0 && via[curX][curY - 1] == 0
                    && !WorldController.solidObjectExists(absX, absY - 1)) {
                tileQueueX[head] = curX;
                tileQueueY[head] = curY - 1;
                head = (head + 1) % pathLength;
                via[curX][curY - 1] = 1;
                cost[curX][curY - 1] = thisCost;
            }
            if (curX > 0 && via[curX - 1][curY] == 0
                    && !WorldController.solidObjectExists(absX - 1, absY)) {
                tileQueueX[head] = curX - 1;
                tileQueueY[head] = curY;
                head = (head + 1) % pathLength;
                via[curX - 1][curY] = 2;
                cost[curX - 1][curY] = thisCost;
            }
            if (curY < 104 - 1 && via[curX][curY + 1] == 0
                    && !WorldController.solidObjectExists(absX, absY + 1)) {
                tileQueueX[head] = curX;
                tileQueueY[head] = curY + 1;
                head = (head + 1) % pathLength;
                via[curX][curY + 1] = 4;
                cost[curX][curY + 1] = thisCost;
            }
            if (curX < 104 - 1 && via[curX + 1][curY] == 0
                    && !WorldController.solidObjectExists(absX + 1, absY)) {
                tileQueueX[head] = curX + 1;
                tileQueueY[head] = curY;
                head = (head + 1) % pathLength;
                via[curX + 1][curY] = 8;
                cost[curX + 1][curY] = thisCost;
            }
            if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0
                    && !WorldController.solidObjectExists(absX - 1, absY - 1)
                    && !WorldController.solidObjectExists(absX - 1, absY)
                    && !WorldController.solidObjectExists(absX, absY - 1)) {
                tileQueueX[head] = curX - 1;
                tileQueueY[head] = curY - 1;
                head = (head + 1) % pathLength;
                via[curX - 1][curY - 1] = 3;
                cost[curX - 1][curY - 1] = thisCost;
            }
            if (curX > 0 && curY < 104 - 1 && via[curX - 1][curY + 1] == 0
                    && !WorldController.solidObjectExists(absX - 1, absY + 1)
                    && !WorldController.solidObjectExists(absX - 1, absY)
                    && !WorldController.solidObjectExists(absX, absY + 1)) {
                tileQueueX[head] = curX - 1;
                tileQueueY[head] = curY + 1;
                head = (head + 1) % pathLength;
                via[curX - 1][curY + 1] = 6;
                cost[curX - 1][curY + 1] = thisCost;
            }
            if (curX < 104 - 1 && curY > 0 && via[curX + 1][curY - 1] == 0
                    && !WorldController.solidObjectExists(absX + 1, absY - 1)
                    && !WorldController.solidObjectExists(absX + 1, absY)
                    && !WorldController.solidObjectExists(absX, absY - 1)) {
                tileQueueX[head] = curX + 1;
                tileQueueY[head] = curY - 1;
                head = (head + 1) % pathLength;
                via[curX + 1][curY - 1] = 9;
                cost[curX + 1][curY - 1] = thisCost;
            }
            if (curX < 104 - 1 && curY < 104 - 1
                    && via[curX + 1][curY + 1] == 0
                    && !WorldController.solidObjectExists(absX + 1, absY + 1)
                    && !WorldController.solidObjectExists(absX + 1, absY)
                    && !WorldController.solidObjectExists(absX, absY + 1)) {
                tileQueueX[head] = curX + 1;
                tileQueueY[head] = curY + 1;
                head = (head + 1) % pathLength;
                via[curX + 1][curY + 1] = 12;
                cost[curX + 1][curY + 1] = thisCost;
            }
        }
        if (foundPath) {
            tail = 0;
            tileQueueX[tail] = curX;
            tileQueueY[tail++] = curY;
            int l5;
            for (int j5 = l5 = via[curX][curY]; curX != localX || curY != localY; j5 = via[curX][curY]) {
                if (j5 != l5) {
                    l5 = j5;
                    tileQueueX[tail] = curX;
                    tileQueueY[tail++] = curY;
                }
                if ((j5 & 2) != 0) {
                    curX++;
                } else if ((j5 & 8) != 0) {
                    curX--;
                }
                if ((j5 & 1) != 0) {
                    curY++;
                } else if ((j5 & 4) != 0) {
                    curY--;
                }
            }
            resetMovement();
            int size = tail--;
            addMoveLocation((regionX - 6) * 8 + tileQueueX[tail], (regionY - 6) * 8 + tileQueueY[tail]);
            for (int i = 1; i < size; i++) {
                addMoveLocation((regionX - 6) * 8 + tileQueueX[--tail], (regionY - 6) * 8 + tileQueueY[tail]);
            }
        }
    }

    public int id() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int x() {
        return x;
    }

    public int y() {
        return y;
    }

    public String username() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public INonBlockingConnection connection() {
        return connection;
    }

    public String hostAddress() {
        return hostAddress;
    }

    public ByteStream in() {
        return in;
    }

    public ByteStream out() {
        return out;
    }

    public ActionSender actionSender() {
        return actionSender;
    }

    public boolean didTeleport() {
        return didTeleport;
    }

    public int walkDir() {
        return walkDir;
    }

    public boolean isVisible() {
        return isVisible;
    }

    public void setIsVisible(boolean isVisible) {
        this.isVisible = isVisible;
    }

    public LinkedList<Player> players() {
        return players;
    }

    public World world() {
        return world;
    }

    public String chat() {
        return chat;
    }

    public boolean chatUpdate() {
        return chatUpdate;
    }

    public int headId() {
        return headId;
    }

    public int torsoId() {
        return torsoId;
    }

    public int legsId() {
        return legsId;
    }

    public boolean appearanceUpdate() {
        return appearanceUpdate;
    }

    public boolean regionUpdate() {
        return regionUpdate;
    }

    public int regionX() {
        return regionX;
    }

    public int regionY() {
        return regionY;
    }
}